/*
 * This file is part of the Try! Engine project.
 *
 * Copyright 2010-2011 Emanuele Bertoldi. All rights reserved.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 * You should have received a copy of the modified BSD License along with this
 * program. If not, see <http://www.opensource.org/licenses/bsd-license.php>
 */

#include "project.h"
#include "projectmodellist.h"
#include <graphicsmanager.h>
#include <rapidxml.h>
#include <QtCore/QFile>

using namespace Try;

Project::Project(
    const QString& name,
    const QString& description,
    const QString& author,
    const QStringList& asset_libs,
    QObject* parent
)
    :   QObject(parent),
        m_name(name),
        m_description(description),
        m_author(author),
        m_assetLibraries(asset_libs)
{
    ProjectModelList::instance()->addProject(this);
}

Project::~Project()
{
    this->deselectAllObjects();

    foreach (Object* obj, this->objects())
        obj->deleteAllProperties();

    this->deleteAllObjects();

    emit closed();

    ProjectModelList::instance()->removeProject(this);
}

QString Project::name() const
{
    return m_name;
}

QString Project::description() const
{
    return m_description;
}

QString Project::author() const
{
    return m_author;
}

QString Project::filename() const
{
    return m_filename;
}

QStringList Project::assetLibraries() const
{
    return m_assetLibraries;
}

int Project::assetLibraryCount() const
{
    return m_assetLibraries.count();
}

QString Project::findAvailableObjectName(const QString& name) const
{
    int count = this->objectCount();
    StringList names = this->objectNames();
    String obj_name = name.toStdString();

    for (int i=0; i<count; i++)
    {
        if (names.at(i) == obj_name)
        {
            QString number = name;
            QString pattern;

            while (!number.isEmpty() && !number.toInt())
            {
                pattern.append(number.left(1));
                number = number.right(number.count()-1);
            }

            if (number.isEmpty())
                return this->findAvailableObjectName(pattern + " #1");

            return this->findAvailableObjectName(QString(pattern + "%1").arg(number.toInt()+1));
        }
    }

    return name;
}

int Project::selectedObjectCount() const
{
    return m_selectedObjects.size();
}

ObjectList Project::selectedObjects() const
{
    return m_selectedObjects;
}

Object* Project::selectedObject(const String& name) const
{
    int id = this->selectedObjectIdFromName(name);

    return this->selectedObject(id);
}

Object* Project::selectedObject(int id) const
{
    if (id < this->selectedObjectCount())
        return m_selectedObjects.at(id);

    return 0;
}

int Project::selectedObjectIdFromName(const String& name) const
{
    for (int i=0; i<this->selectedObjectCount(); i++)
    {
        Object* object = m_selectedObjects.at(i);
        if (object && object->name() == name)
            return i;
    }

    return -1;
}

bool Project::isObjectSelected(Object* object) const
{
    for (int i=0; i<this->selectedObjectCount(); i++)
    {
        if (m_selectedObjects.at(i) == object)
            return true;
    }

    return false;
}

bool Project::isObjectSelected(const String& name) const
{
    return this->isObjectSelected(this->object(name));
}

bool Project::isObjectSelected(int id) const
{
    return this->isObjectSelected(this->object(id));
}

bool Project::load(const QString& filename)
{
    if (!filename.isEmpty() || !m_filename.isEmpty())
    {
        QString fn = filename;
        if (fn.isEmpty())
            fn = m_filename;

        if (!fn.isEmpty())
        {
            QFile file(fn);

            if (file.open(QIODevice::ReadOnly))
            {
                this->deleteAllObjects();

                QByteArray content = file.readAll();

                XmlDoc doc;
                doc.parse< rapidxml::parse_validate_closing_tags >(content.data());
                XmlNode* proj_node = doc.first_node("project");

                if (proj_node)
                {
                    m_name = proj_node->first_attribute("name")->value();
                    m_author = proj_node->first_attribute("author")->value();
                    m_description = proj_node->first_attribute("description")->value();
                    m_filename = fn;

                    ObjectList objects = Object::deserialize(proj_node->first_node("object"));
                    this->watchObjects(objects);

                    emit loaded();

                    return true;
                }
            }
        }
    }

    return false;
}

bool Project::save(const QString& filename)
{
    if (!filename.isEmpty() || !m_filename.isEmpty())
    {
        QString fn = filename;
        if (fn.isEmpty())
            fn = m_filename;

        if (!fn.isEmpty())
        {
            QFile file(fn);

            if (file.open(QIODevice::WriteOnly))
            {
                // TODO: fill data.
                QByteArray data;

                data += QString("<project name=\"%1\" author=\"%2\" description=\"%3\">\n")
                        .arg(this->name())
                        .arg(this->author())
                        .arg(this->description());

                foreach (Object* obj, m_objects)
                    data.append(obj->serialize().c_str());

                data += "</project>\n";

                file.write(data);

                m_filename = fn;

                emit saved();

                return true;
            }
        }
    }

    return false;
}

void Project::setName(const QString& name)
{
    if (name != m_name)
    {
        m_name = name;

        emit changed();
    }
}

void Project::setDescription(const QString& description)
{
    if (description != m_description)
    {
        m_description = description;

        emit changed();
    }
}

void Project::setAuthor(const QString& author)
{
    if (author != m_author)
    {
        m_author = author;

        emit changed();
    }
}

void Project::addAssetLibrary(const QString& filename)
{
    if (!m_assetLibraries.contains(filename))
    {
        m_assetLibraries.append(filename);

        emit changed();
    }
}

void Project::removeAssetLibrary(const QString& filename)
{
    if (m_assetLibraries.contains(filename))
    {
        m_assetLibraries.removeAll(filename);

        emit changed();
    }
}

Object* Project::createObject()
{
    Object* object = new Object(this->findAvailableObjectName("New Object").toStdString());

    this->watchObject(object);

    return object;
}

Object* Project::copyObject(Object* object)
{
    if (object && this->isWatching(object))
    {
        Object* copy = object->copy();

        this->watchObject(copy);

        return copy;
    }

    return 0;
}

Object* Project::copyObject(const String& name)
{
    int id = this->objectIdFromName(name);

    return this->copyObject(id);
}

Object* Project::copyObject(int id)
{
    Object* object = this->object(id);

    return this->copyObject(object);
}

void Project::selectObject(Object* object)
{
    if (!this->isObjectSelected(object))
    {
        m_selectedObjects.push_back(object);

        emit selectionChanged();
    }
}

void Project::selectObject(const String& name)
{
    this->selectObject(this->object(name));
}

void Project::selectObject(int id)
{
    this->selectObject(this->object(id));
}

void Project::selectAllObjects()
{
    foreach (Object* object, this->objects())
        this->selectObject(object);

    emit selectionChanged();
}

void Project::deselectObject(Object* object)
{
    if (this->isObjectSelected(object))
    {
        int pos = this->selectedObjectIdFromName(object->name());

        m_selectedObjects.erase(m_selectedObjects.begin()+pos);

        emit selectionChanged();
    }
}

void Project::deselectObject(const String& name)
{
    this->deselectObject(this->object(name));
}

void Project::deselectObject(int id)
{
    this->deselectObject(this->object(id));
}

void Project::deselectAllObjects()
{
    foreach (Object* object, this->selectedObjects())
        this->deselectObject(object);

    emit selectionChanged();
}

void Project::onMessage(const Message& msg, Object* sender, Property* property)
{
    Q_UNUSED(property)

    QString msg_name(msg.name().c_str());

    QStringList accepted_msgs;
    accepted_msgs.append("renamed");
    accepted_msgs.append("propertyAdded");
    accepted_msgs.append("propertyRemoved");
    accepted_msgs.append("propertyDeleted");
    accepted_msgs.append("behaviorAdded");
    accepted_msgs.append("removingBehavior");

    if (accepted_msgs.contains(msg_name))
        emit changed();

    if (msg_name == "removingBehavior")
        this->deselectObject(sender);
}
